/*
 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     bstefanescu
 */
package org.nuxeo.gwt.habyt.upload.client.core;

import org.nuxeo.gwt.habyt.upload.client.FileChanges;
import org.nuxeo.gwt.habyt.upload.client.FileEvent;
import org.nuxeo.gwt.habyt.upload.client.FileEventHandler;
import org.nuxeo.gwt.habyt.upload.client.FileRef;
import org.nuxeo.gwt.habyt.upload.client.FileWidgetProvider;
import org.nuxeo.gwt.habyt.upload.client.Uploader;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.RequestTimeoutException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
 * 
 */
public class DefaultUploader extends SimplePanel implements Uploader {

    public static String getServerUrl(String path) {
        String url;
        if (path.indexOf(':') == -1) {
            url = GWT.getHostPageBaseURL();
            if (url.endsWith("/")) {
                if (path.startsWith("/")) {
                    url = url.substring(0, url.length() - 1) + path;
                } else {
                    url = url + path;
                }
            } else {
                if (path.startsWith("/")) {
                    url = url + path;
                } else {
                    url = url + '/' + path;
                }
            }
        } else {
            url = path;
        }
        if (url.endsWith("/")) {
            url = url.substring(0, url.length() - 1);
        }
        return url;
    }

    protected FileWidgetProvider provider;

    protected String serviceUrl;

    protected int progressTimeout = 1500;

    protected UploadQueue queue;

    protected boolean isMultiUpload = true;

    protected HandlerManager handlers;

    protected FlowPanel formPanel;

    /**
     * record changes on files like stored files which was removed and new files
     * which was uploaded.
     */
    protected FileChanges changes;

    /**
     * Create a new widget given the servlet URL that will handle requests.
     * 
     * If the baseUrl is a relative path then it will be resolved using the
     * {@link #getServerUrl(String)} method.
     * 
     * @param baseUrl
     */
    public DefaultUploader(String baseUrl) {
        this(baseUrl, null);
    }

    /**
     * Create a new widget given the servlet URL that will handle requests and a
     * file widget provider to customize the input file widget.
     * 
     * If the baseUrl is a relative path then it will be resolved using the
     * {@link #getServerUrl(String)} method.
     * 
     * @param baseUrl
     * @param fileWidgetProvider
     */
    public DefaultUploader(String path, FileWidgetProvider fileWidgetProvider) {
        setStyleName("upload-form-view");
        formPanel = new FlowPanel();
        setWidget(formPanel);
        changes = new FileChanges();
        handlers = new HandlerManager(this);
        queue = new UploadQueue(this);
        this.serviceUrl = getServerUrl(path);
        this.provider = fileWidgetProvider;
        resetForm();
    }

    public void resetForm() {
        formPanel.add(new UploadForm(this, provider));
    }

    /**
     * Customization method. Override this to change the files row container.
     * The default is a {@link VerticalPanel}
     * 
     * @return
     */
    protected Panel createFilesPanel() {
        return new VerticalPanel();
    }

    public FileChanges getChanges() {
        return changes;
    }

    public HandlerRegistration addFileEventHandler(FileEventHandler handler) {
        return handlers.addHandler(FileEvent.getType(), handler);
    }

    public UploadQueue getUploadQueue() {
        return queue;
    }

    @Override
    public Widget getWidget() {
        return this;
    }

    public void setMultiUpload(boolean isMultiUpload) {
        this.isMultiUpload = isMultiUpload;
    }

    public boolean isMultiUpload() {
        return isMultiUpload;
    }

    public void setProgressTimeout(int progressTimeout) {
        this.progressTimeout = progressTimeout;
    }

    public int getProgressTimeout() {
        return progressTimeout;
    }

    @Override
    public String getUploadUrl(FileRef ref) {
        return this.serviceUrl + "/" + ref.getId();
    }

    @Override
    public String getRemoveUrl(String id) {
        return this.serviceUrl + "?action=delete&id=" + id;
    }

    @Override
    public String getViewUrl(String id) {
        return this.serviceUrl + "?action=get&id=" + id;
    }

    @Override
    public String getProgressUrl(String id) {
        return this.serviceUrl + "?action=progress&id=" + id;
    }

    @Override
    public FileWidgetProvider getFileWidgetProvider() {
        return provider;
    }

    @Override
    public boolean hasActiveUploads() {
        return !queue.isEmpty();
    }

    public boolean isFileQueuedOrUploaded(String fileName) {
        if (queue.hasFileWithName(fileName)) {
            return true;
        }
        for (FileRef added : changes.getAddedFiles()) {
            if (fileName.equals(added.getName())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean addUploadRequest(UploadForm form) {
        FileRef ref = form.getFileRef();
        // avoid duplicates
        if (isFileQueuedOrUploaded(ref.getName())) {
            Window.alert("File already queued");
            form.destroy();
            resetForm();
            return false;
        }
        ref.setProgress(0);
        ref.setStatus(FileRef.QUEUED);
        if (isMultiUpload) {
            resetForm();
        }
        updateStatus(ref);
        queue.add(form);
        return true;
    }

    @Override
    public boolean onSubmit(UploadForm form) {
        FileRef ref = form.getFileRef();
        ref.setStatus(FileRef.UPLOADING);
        updateStatus(ref);
        return true;
    }

    @Override
    public void onSubmitDone(UploadForm form) {
        FileRef ref = form.getFileRef();
        ref.setProgress(100);
        ref.setStatus(FileRef.UPLOADED);
        updateStatus(ref);
        queue.stopCurrentTask();
        changes.getAddedFiles().add(ref);
        handlers.fireEvent(new FileEvent(ref));
    }

    @Override
    public void onSubmitError(UploadForm form) {
        FileRef ref = form.getFileRef();
        ref.setStatus(FileRef.ERROR);
        updateStatus(ref);
        queue.stopCurrentTask();
        handlers.fireEvent(new FileEvent(ref));
    }

    public void removeFile(FileRef ref) {
        boolean isStored = ref.getStatus() == FileRef.STORED;
        boolean isError = ref.getStatus() == FileRef.ERROR;
        if (isStored) {
            changes.getRemovedFiles().add(ref);
        } else {
            changes.getAddedFiles().remove(ref);
        }
        ref.setStatus(FileRef.REMOVED);
        if (!isMultiUpload) {
            formPanel.clear();
            setVisible(true);
            resetForm();
        }
        queue.remove(ref);

        if (!isStored && !isError) {
            removeFromServer(ref);
        }
        // TODO should fire event from server callback?
        updateStatus(ref);
    }

    public void updateStatus(FileRef ref) {
        handlers.fireEvent(new FileEvent(ref));
    }

    public void removeFromServer(final FileRef ref) {
        try {
            RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,
                    getRemoveUrl(ref.getId()));
            rb.setCallback(new RequestCallback() {
                @Override
                public void onResponseReceived(Request request,
                        Response response) {
                    // updateStatus(ref);
                    // the file was already removed from view
                }

                @Override
                public void onError(Request request, Throwable exception) {
                    // TODO: do nothing from now
                }
            });
            rb.send();
        } catch (RequestException e) {
            // do nothing
        }
    }

    @Override
    public void checkProgress(final FileRef ref) {
        try {
            RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,
                    getProgressUrl(ref.getId()));
            rb.setTimeoutMillis(progressTimeout);
            rb.setCallback(new RequestCallback() {
                @Override
                public void onResponseReceived(Request request,
                        Response response) {
                    String h = response.getHeader("X-FileUpload-Progress");
                    // System.out.println("client received: " + h);
                    if (h != null) {
                        ref.setProgress(Integer.parseInt(h.trim()));
                    } else {
                        ref.setStatus(FileRef.ERROR);
                    }
                    updateStatus(ref);
                }

                @Override
                public void onError(Request request, Throwable exception) {
                    if (!(exception instanceof RequestTimeoutException)) {
                        ref.setStatus(FileRef.ERROR);
                        updateStatus(ref);
                    }
                    // otherwise ignore and keep trying
                    // System.out.println("retry");
                }
            });
            rb.send();
        } catch (RequestException e) {
            ref.setStatus(FileRef.ERROR);
            updateStatus(ref);
        }

    }

}
